]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
popupMenu: Add and use GtkMenuTracker to build the remote menu
authorJasper St. Pierre <jstpierre@mecheye.net>
Sat, 20 Apr 2013 01:38:47 +0000 (21:38 -0400)
committerJasper St. Pierre <jstpierre@mecheye.net>
Tue, 23 Apr 2013 19:50:36 +0000 (15:50 -0400)
This simplifies the code required to build remote menus and
put all the items in the right place, and makes us share our
implementation with GTK+.

https://bugzilla.gnome.org/show_bug.cgi?id=698427

js/ui/popupMenu.js
src/Makefile.am
src/gtkmenutracker.c [new file with mode: 0644]
src/gtkmenutracker.h [new file with mode: 0644]
src/shell-menu-tracker.c [new file with mode: 0644]
src/shell-menu-tracker.h [new file with mode: 0644]

index 11c3ab513600a023b39320561a403732862387fd..77196e3c704c4cc012dfcd474126fdad7c62b808 100644 (file)
@@ -1799,8 +1799,8 @@ const RemoteMenu = new Lang.Class({
         this.model = model;
         this.actionGroup = actionGroup;
 
-        this._actions = { };
-        this._modelChanged(this.model, 0, 0, this.model.get_n_items(), this);
+        this._actions = {};
+        this._trackMenu(model, this);
 
         this._actionStateChangeId = this.actionGroup.connect('action-state-changed', Lang.bind(this, this._actionStateChanged));
         this._actionEnableChangeId = this.actionGroup.connect('action-enabled-changed', Lang.bind(this, this._actionEnabledChanged));
@@ -1858,7 +1858,7 @@ const RemoteMenu = new Lang.Class({
                 break;
             default:
                 log('Action "%s" has state of type %s, which is not supported'.format(action_id, action.state.get_type_string()));
-                return [null, false];
+                return;
             }
         } else {
             target = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null);
@@ -1878,8 +1878,18 @@ const RemoteMenu = new Lang.Class({
             if (pos != -1)
                 action.items.splice(pos, 1);
         }));
+    },
 
-        return [item, false];
+    _trackMenu: function(model, item) {
+        item._tracker = Shell.MenuTracker.new(model,
+                                              null, /* action namespace */
+                                              Lang.bind(this, this._insertItem, item),
+                                              Lang.bind(this, this._removeItem, item));
+
+        item.connect('destroy', function() {
+            item._tracker.destroy();
+            item._tracker = null;
+        });
     },
 
     _createMenuItem: function(model, index) {
@@ -1888,26 +1898,11 @@ const RemoteMenu = new Lang.Class({
         // remove all underscores that are not followed by another underscore
         label = label.replace(/_([^_])/, '$1');
 
-        let section_link = model.get_item_link(index, Gio.MENU_LINK_SECTION);
-        if (section_link) {
-            let item = new PopupMenuSection();
-            if (label) {
-                let title = new PopupMenuItem(label, { reactive: false,
-                                                       style_class: 'popup-subtitle-menu-item' });
-                item._titleMenuItem = title;
-                title._ignored = true;
-                item.addMenuItem(title);
-            }
-            this._modelChanged(section_link, 0, 0, section_link.get_n_items(), item);
-            return [item, true];
-        }
-
-        let submenu_link = model.get_item_link(index, Gio.MENU_LINK_SUBMENU);
-
-        if (submenu_link) {
+        let submenuModel = model.get_item_link(index, Gio.MENU_LINK_SUBMENU);
+        if (submenuModel) {
             let item = new PopupSubMenuMenuItem(label);
-            this._modelChanged(submenu_link, 0, 0, submenu_link.get_n_items(), item.menu);
-            return [item, false];
+            this._trackMenu(submenuModel, item.menu);
+            return item;
         }
 
         let item = new PopupMenuItem(label);
@@ -1918,7 +1913,7 @@ const RemoteMenu = new Lang.Class({
 
         if (this.actionGroup.has_action(action_id)) {
             this._actionAdded(model, item, index);
-            return [item, false];
+            return item;
         }
 
         let signalId = this.actionGroup.connect('action-added', Lang.bind(this, function(actionGroup, actionName) {
@@ -1931,85 +1926,6 @@ const RemoteMenu = new Lang.Class({
         return [item, false];
     },
 
-    _modelChanged: function(model, position, removed, added, target) {
-        let j, k;
-        let j0, k0;
-
-        let currentItems = target._getMenuItems();
-
-        k0 = 0;
-        // skip ignored items at the beginning
-        while (k0 < currentItems.length && currentItems[k0]._ignored)
-            k0++;
-        // find the right menu item matching the model item
-        for (j0 = 0; k0 < currentItems.length && j0 < position; j0++, k0++) {
-            if (currentItems[k0]._ignored)
-                k0++;
-        }
-
-        if (removed == -1) {
-            // special flag to indicate we should destroy everything
-            for (k = k0; k < currentItems.length; k++)
-                currentItems[k].destroy();
-        } else {
-            for (j = j0, k = k0; k < currentItems.length && j < j0 + removed; j++, k++) {
-                currentItems[k].destroy();
-
-                if (currentItems[k]._ignored)
-                    j--;
-            }
-        }
-
-        for (j = j0, k = k0; j < j0 + added; j++, k++) {
-            let [item, addSeparator] = this._createMenuItem(model, j);
-
-            if (!item)
-                continue;
-
-            // separators must be added in the parent to make autohiding work
-            if (addSeparator) {
-                let separator = new PopupSeparatorMenuItem();
-                item.separators.push(separator);
-                separator._ignored = true;
-                target.addMenuItem(separator, k+1);
-                k++;
-            }
-
-            target.addMenuItem(item, k);
-
-            if (addSeparator) {
-                let separator = new PopupSeparatorMenuItem();
-                item.separators.push(separator);
-                separator._ignored = true;
-                target.addMenuItem(separator, k+1);
-                k++;
-            }
-        }
-
-        if (!model._changedId) {
-            model._changedId = model.connect('items-changed', Lang.bind(this, this._modelChanged, target));
-            model._destroyId = target.connect('destroy', function() {
-                if (model._changedId)
-                    model.disconnect(model._changedId);
-                if (model._destroyId)
-                    target.disconnect(model._destroyId);
-                model._changedId = 0;
-                model._destroyId = 0;
-            });
-        }
-
-        if (target instanceof PopupMenuSection) {
-            if (target._titleMenuItem)
-                target.actor.visible = target.numMenuItems != 1;
-            else
-                target.actor.visible = target.numMenuItems != 0;
-        } else {
-            let sourceItem = target.sourceActor._delegate;
-            if (sourceItem instanceof PopupSubMenuMenuItem)
-                sourceItem.actor.visible = target.numMenuItems != 0;
-        }
-    },
-
     _actionStateChanged: function(actionGroup, action_id) {
         let action = this._actions[action_id];
         if (!action)
@@ -2047,7 +1963,23 @@ const RemoteMenu = new Lang.Class({
                 item.actor.reactive = item.actor.can_focus = action.enabled;
             }
         }
-    }
+    },
+
+    _insertItem: function(position, model, item_index, action_namespace, is_separator, target) {
+        let item;
+
+        if (is_separator)
+            item = new PopupSeparatorMenuItem();
+        else
+            item = this._createMenuItem(model, item_index);
+
+        target.addMenuItem(item, position);
+    },
+
+    _removeItem: function(position, target) {
+        let items = target._getMenuItems();
+        items[position].destroy();
+    },
 });
 
 /* Basic implementation of a menu manager.
index 5afff40696c2ce8d7080aa3d4075093adb916690..58e92bd35dbbba3a854fb8ed4744de947d2f5774 100644 (file)
@@ -135,7 +135,10 @@ shell_private_sources = \
        gactionobservable.h             \
        gactionobservable.c             \
        gactionobserver.h               \
-       gactionobserver.c
+       gactionobserver.c               \
+       gtkmenutracker.c                \
+       gtkmenutracker.h                \
+       $(NULL)
 
 libgnome_shell_la_SOURCES =            \
        $(shell_built_sources)          \
@@ -158,6 +161,8 @@ libgnome_shell_la_SOURCES =         \
        shell-invert-lightness-effect.c \
        shell-keyring-prompt.h          \
        shell-keyring-prompt.c          \
+       shell-menu-tracker.c            \
+       shell-menu-tracker.h            \
        shell-mount-operation.c         \
        shell-network-agent.c           \
        shell-perf-log.c                \
diff --git a/src/gtkmenutracker.c b/src/gtkmenutracker.c
new file mode 100644 (file)
index 0000000..f49457f
--- /dev/null
@@ -0,0 +1,434 @@
+/*
+ * Copyright © 2013 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "config.h"
+
+#include "gtkmenutracker.h"
+
+typedef struct _GtkMenuTrackerSection GtkMenuTrackerSection;
+
+struct _GtkMenuTracker
+{
+  GtkMenuTrackerInsertFunc  insert_func;
+  GtkMenuTrackerRemoveFunc  remove_func;
+  gpointer                  user_data;
+
+  GtkMenuTrackerSection    *toplevel;
+};
+
+struct _GtkMenuTrackerSection
+{
+  GMenuModel *model;
+  GSList     *items;
+  gchar      *action_namespace;
+
+  guint       with_separators : 1;
+  guint       has_separator   : 1;
+
+  gulong      handler;
+};
+
+static GtkMenuTrackerSection *  gtk_menu_tracker_section_new    (GtkMenuTracker        *tracker,
+                                                                 GMenuModel            *model,
+                                                                 gboolean               with_separators,
+                                                                 gint                   offset,
+                                                                 const gchar           *action_namespace);
+static void                    gtk_menu_tracker_section_free    (GtkMenuTrackerSection *section);
+
+static GtkMenuTrackerSection *
+gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section,
+                                     GMenuModel            *model,
+                                     gint                  *offset)
+{
+  GSList *item;
+
+  if (section->has_separator)
+    (*offset)++;
+
+  if (section->model == model)
+    return section;
+
+  for (item = section->items; item; item = item->next)
+    {
+      GtkMenuTrackerSection *subsection = item->data;
+
+      if (subsection)
+        {
+          GtkMenuTrackerSection *found_section;
+
+          found_section = gtk_menu_tracker_section_find_model (subsection, model, offset);
+
+          if (found_section)
+            return found_section;
+        }
+      else
+        (*offset)++;
+    }
+
+  return FALSE;
+}
+
+/* this is responsible for syncing the showing of a separator for a
+ * single subsection (and its children).
+ *
+ * we only ever show separators if we have _actual_ children (ie: we do
+ * not show a separator if the section contains only empty child
+ * sections).  it's difficult to determine this on-the-fly, so we have
+ * this separate function to come back later and figure it out.
+ *
+ * 'section' is that section.
+ *
+ * 'tracker' is passed in so that we can emit callbacks when we decide
+ * to add/remove separators.
+ *
+ * 'offset' is passed in so we know which position to emit in our
+ * callbacks.  ie: if we add a separator right at the top of this
+ * section then we would emit it with this offset.  deeper inside, we
+ * adjust accordingly.
+ *
+ * could_have_separator is true in two situations:
+ *
+ *  - our parent section had with_separators defined and we are not the
+ *    first section (ie: we should add a separator if we have content in
+ *    order to divide us from the items above)
+ *
+ *  - if we had a 'label' attribute set for this section
+ *
+ * parent_model and parent_index are passed in so that we can give them
+ * to the insertion callback so that it can see the label (and anything
+ * else that happens to be defined on the section).
+ *
+ * we iterate each item in ourselves.  for subsections, we recursively
+ * run ourselves to sync separators.  after we are done, we notice if we
+ * have any items in us or if we are completely empty and sync if our
+ * separator is shown or not.
+ */
+static gint
+gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section,
+                                          GtkMenuTracker        *tracker,
+                                          gint                   offset,
+                                          gboolean               could_have_separator,
+                                          GMenuModel            *parent_model,
+                                          gint                   parent_index)
+{
+  gboolean should_have_separator;
+  gint n_items = 0;
+  GSList *item;
+  gint i = 0;
+
+  for (item = section->items; item; item = item->next)
+    {
+      GtkMenuTrackerSection *subsection = item->data;
+
+      if (subsection)
+        {
+          gboolean could_have_separator;
+
+          could_have_separator = (section->with_separators && i > 0) ||
+                                 g_menu_model_get_item_attribute (section->model, i, "label", "s", NULL);
+
+          n_items += gtk_menu_tracker_section_sync_separators (subsection, tracker, offset + n_items,
+                                                               could_have_separator, section->model, i);
+        }
+      else
+        n_items++;
+
+      i++;
+    }
+
+  should_have_separator = could_have_separator && n_items != 0;
+
+  if (should_have_separator > section->has_separator)
+    {
+      /* Add a separator */
+      (* tracker->insert_func) (offset, parent_model, parent_index, NULL, TRUE, tracker->user_data);
+      section->has_separator = TRUE;
+    }
+  else if (should_have_separator < section->has_separator)
+    {
+      /* Remove a separator */
+      (* tracker->remove_func) (offset, tracker->user_data);
+      section->has_separator = FALSE;
+    }
+
+  n_items += section->has_separator;
+
+  return n_items;
+}
+
+static gint
+gtk_menu_tracker_section_measure (GtkMenuTrackerSection *section)
+{
+  GSList *item;
+  gint n_items;
+
+  if (section == NULL)
+    return 1;
+
+  n_items = 0;
+
+  if (section->has_separator)
+    n_items++;
+
+  for (item = section->items; item; item = item->next)
+    n_items += gtk_menu_tracker_section_measure (item->data);
+
+  return n_items;
+}
+
+static void
+gtk_menu_tracker_remove_items (GtkMenuTracker  *tracker,
+                               GSList         **change_point,
+                               gint             offset,
+                               gint             n_items)
+{
+  gint i;
+
+  for (i = 0; i < n_items; i++)
+    {
+      GtkMenuTrackerSection *subsection;
+      gint n;
+
+      subsection = (*change_point)->data;
+      *change_point = g_slist_delete_link (*change_point, *change_point);
+
+      n = gtk_menu_tracker_section_measure (subsection);
+      gtk_menu_tracker_section_free (subsection);
+
+      while (n--)
+        (* tracker->remove_func) (offset, tracker->user_data);
+    }
+}
+
+static void
+gtk_menu_tracker_add_items (GtkMenuTracker         *tracker,
+                            GtkMenuTrackerSection  *section,
+                            GSList                **change_point,
+                            gint                    offset,
+                            GMenuModel             *model,
+                            gint                    position,
+                            gint                    n_items)
+{
+  while (n_items--)
+    {
+      GMenuModel *submenu;
+
+      submenu = g_menu_model_get_item_link (model, position + n_items, G_MENU_LINK_SECTION);
+      g_assert (submenu != model);
+      if (submenu != NULL)
+        {
+          GtkMenuTrackerSection *subsection;
+          gchar *action_namespace = NULL;
+
+          g_menu_model_get_item_attribute (model, position + n_items,
+                                           G_MENU_ATTRIBUTE_ACTION_NAMESPACE, "s", &action_namespace);
+
+          if (section->action_namespace)
+            {
+              gchar *namespace;
+
+              namespace = g_strjoin (".", section->action_namespace, action_namespace, NULL);
+              subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, namespace);
+              g_free (namespace);
+            }
+          else
+            subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, section->action_namespace);
+
+          *change_point = g_slist_prepend (*change_point, subsection);
+          g_free (action_namespace);
+          g_object_unref (submenu);
+        }
+      else
+        {
+          (* tracker->insert_func) (offset, model, position + n_items,
+                                    section->action_namespace, FALSE, tracker->user_data);
+          *change_point = g_slist_prepend (*change_point, NULL);
+        }
+    }
+}
+
+static void
+gtk_menu_tracker_model_changed (GMenuModel *model,
+                                gint        position,
+                                gint        removed,
+                                gint        added,
+                                gpointer    user_data)
+{
+  GtkMenuTracker *tracker = user_data;
+  GtkMenuTrackerSection *section;
+  GSList **change_point;
+  gint offset = 0;
+  gint i;
+
+  /* First find which section the changed model corresponds to, and the
+   * position of that section within the overall menu.
+   */
+  section = gtk_menu_tracker_section_find_model (tracker->toplevel, model, &offset);
+
+  /* Next, seek through that section to the change point.  This gives us
+   * the correct GSList** to make the change to and also finds the final
+   * offset at which we will make the changes (by measuring the number
+   * of items within each item of the section before the change point).
+   */
+  change_point = &section->items;
+  for (i = 0; i < position; i++)
+    {
+      offset += gtk_menu_tracker_section_measure ((*change_point)->data);
+      change_point = &(*change_point)->next;
+    }
+
+  /* We remove items in order and add items in reverse order.  This
+   * means that the offset used for all inserts and removes caused by a
+   * single change will be the same.
+   *
+   * This also has a performance advantage: GtkMenuShell stores the
+   * menu items in a linked list.  In the case where we are creating a
+   * menu for the first time, adding the items in reverse order means
+   * that we only ever insert at index zero, prepending the list.  This
+   * means that we can populate in O(n) time instead of O(n^2) that we
+   * would do by appending.
+   */
+  gtk_menu_tracker_remove_items (tracker, change_point, offset, removed);
+  gtk_menu_tracker_add_items (tracker, section, change_point, offset, model, position, added);
+
+  /* The offsets for insertion/removal of separators will be all over
+   * the place, however...
+   */
+  gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0);
+}
+
+static void
+gtk_menu_tracker_section_free (GtkMenuTrackerSection *section)
+{
+  if (section == NULL)
+    return;
+
+  g_signal_handler_disconnect (section->model, section->handler);
+  g_slist_free_full (section->items, (GDestroyNotify) gtk_menu_tracker_section_free);
+  g_free (section->action_namespace);
+  g_object_unref (section->model);
+  g_slice_free (GtkMenuTrackerSection, section);
+}
+
+static GtkMenuTrackerSection *
+gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
+                              GMenuModel     *model,
+                              gboolean        with_separators,
+                              gint            offset,
+                              const gchar    *action_namespace)
+{
+  GtkMenuTrackerSection *section;
+
+  section = g_slice_new0 (GtkMenuTrackerSection);
+  section->model = g_object_ref (model);
+  section->with_separators = with_separators;
+  section->action_namespace = g_strdup (action_namespace);
+
+  gtk_menu_tracker_add_items (tracker, section, &section->items, offset, model, 0, g_menu_model_get_n_items (model));
+  section->handler = g_signal_connect (model, "items-changed", G_CALLBACK (gtk_menu_tracker_model_changed), tracker);
+
+  return section;
+}
+
+/*< private >
+ * gtk_menu_tracker_new:
+ * @model: the model to flatten
+ * @with_separators: if the toplevel should have separators (ie: TRUE
+ *   for menus, FALSE for menubars)
+ * @action_namespace: the passed-in action namespace
+ * @insert_func: insert callback
+ * @remove_func: remove callback
+ * @user_data user data for callbacks
+ *
+ * Creates a GtkMenuTracker for @model, holding a ref on @model for as
+ * long as the tracker is alive.
+ *
+ * This flattens out the model, merging sections and inserting
+ * separators where appropriate.  It monitors for changes and performs
+ * updates on the fly.  It also handles action_namespace for subsections
+ * (but you will need to handle it yourself for submenus).
+ *
+ * When the tracker is first created, @insert_func will be called many
+ * times to populate the menu with the initial contents of @model
+ * (unless it is empty), before gtk_menu_tracker_new() returns.  For
+ * this reason, the menu that is using the tracker ought to be empty
+ * when it creates the tracker.
+ *
+ * Future changes to @model will result in more calls to @insert_func
+ * and @remove_func.
+ *
+ * The position argument to both functions is the linear 0-based
+ * position in the menu at which the item in question should be inserted
+ * or removed.
+ *
+ * For @insert_func, @model and @item_index are used to get the
+ * information about the menu item to insert.  @action_namespace is the
+ * action namespace that actions referred to from that item should place
+ * themselves in.  Note that if the item is a submenu and the
+ * "action-namespace" attribute is defined on the item, it will _not_ be
+ * applied to the @action_namespace argument as it is meant for the
+ * items inside of the submenu, not the submenu item itself.
+ *
+ * @is_separator is set to %TRUE in case the item being added is a
+ * separator.  @model and @item_index will still be meaningfully set in
+ * this case -- to the section menu item corresponding to the separator.
+ * This is useful if the section specifies a label, for example.  If
+ * there is an "action-namespace" attribute on this menu item then it
+ * should be ignored by the consumer because #GtkMenuTracker has already
+ * handled it.
+ *
+ * When using #GtkMenuTracker there is no need to hold onto @model or
+ * monitor it for changes.  The model will be unreffed when
+ * gtk_menu_tracker_free() is called.
+ */
+GtkMenuTracker *
+gtk_menu_tracker_new (GMenuModel               *model,
+                      gboolean                  with_separators,
+                      const gchar              *action_namespace,
+                      GtkMenuTrackerInsertFunc  insert_func,
+                      GtkMenuTrackerRemoveFunc  remove_func,
+                      gpointer                  user_data)
+{
+  GtkMenuTracker *tracker;
+
+  tracker = g_slice_new (GtkMenuTracker);
+  tracker->insert_func = insert_func;
+  tracker->remove_func = remove_func;
+  tracker->user_data = user_data;
+
+  tracker->toplevel = gtk_menu_tracker_section_new (tracker, model, with_separators, 0, action_namespace);
+  gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0);
+
+  return tracker;
+}
+
+/*< private >
+ * gtk_menu_tracker_free:
+ * @tracker: a #GtkMenuTracker
+ *
+ * Frees the tracker, ...
+ */
+void
+gtk_menu_tracker_free (GtkMenuTracker *tracker)
+{
+  gtk_menu_tracker_section_free (tracker->toplevel);
+  g_slice_free (GtkMenuTracker, tracker);
+}
diff --git a/src/gtkmenutracker.h b/src/gtkmenutracker.h
new file mode 100644 (file)
index 0000000..51810a1
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright © 2013 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __GTK_MENU_TRACKER_H__
+#define __GTK_MENU_TRACKER_H__
+
+#include <gio/gio.h>
+
+typedef struct _GtkMenuTracker GtkMenuTracker;
+
+typedef void         (* GtkMenuTrackerInsertFunc)       (gint                      position,
+                                                         GMenuModel               *model,
+                                                         gint                      item_index,
+                                                         const gchar              *action_namespace,
+                                                         gboolean                  is_separator,
+                                                         gpointer                  user_data);
+
+typedef void         (* GtkMenuTrackerRemoveFunc)       (gint                      position,
+                                                         gpointer                  user_data);
+
+
+G_GNUC_INTERNAL
+GtkMenuTracker *        gtk_menu_tracker_new            (GMenuModel               *model,
+                                                         gboolean                  with_separators,
+                                                         const gchar              *action_namespace,
+                                                         GtkMenuTrackerInsertFunc  insert_func,
+                                                         GtkMenuTrackerRemoveFunc  remove_func,
+                                                         gpointer                  user_data);
+
+G_GNUC_INTERNAL
+void                    gtk_menu_tracker_free           (GtkMenuTracker           *tracker);
+
+#endif /* __GTK_MENU_TRACKER_H__ */
diff --git a/src/shell-menu-tracker.c b/src/shell-menu-tracker.c
new file mode 100644 (file)
index 0000000..788605e
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2013 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Written by:
+ *     Jasper St. Pierre <jstpierre@mecheye.net>
+ */
+
+#include "config.h"
+
+#include "shell-menu-tracker.h"
+#include "gtkmenutracker.h"
+
+struct _ShellMenuTracker
+{
+  guint ref_count;
+
+  GtkMenuTracker *tracker;
+
+  ShellMenuTrackerInsertFunc insert_func;
+  gpointer insert_user_data;
+  GDestroyNotify insert_notify;
+  ShellMenuTrackerRemoveFunc remove_func;
+  gpointer remove_user_data;
+  GDestroyNotify remove_notify;
+};
+
+static void
+shell_menu_tracker_insert_func (gint position,
+                                GMenuModel *model,
+                                gint item_index,
+                                const gchar *action_namespace,
+                                gboolean is_separator,
+                                gpointer user_data)
+{
+  ShellMenuTracker *tracker = (ShellMenuTracker *) user_data;
+  tracker->insert_func (position, model, item_index,
+                        action_namespace, is_separator,
+                        tracker->insert_user_data);
+}
+
+static void
+shell_menu_tracker_remove_func (gint position,
+                                gpointer user_data)
+{
+  ShellMenuTracker *tracker = (ShellMenuTracker *) user_data;
+  tracker->remove_func (position, tracker->remove_user_data);
+}
+
+/**
+ * shell_menu_tracker_new:
+ * @model:
+ * @action_namespace: (allow-none):
+ * @insert_func:
+ * @insert_user_data:
+ * @insert_notify:
+ * @remove_func:
+ * @remove_user_data:
+ * @remove_notify:
+ */
+ShellMenuTracker *
+shell_menu_tracker_new (GMenuModel                 *model,
+                        const gchar                *action_namespace,
+                        ShellMenuTrackerInsertFunc  insert_func,
+                        gpointer                    insert_user_data,
+                        GDestroyNotify              insert_notify,
+                        ShellMenuTrackerRemoveFunc  remove_func,
+                        gpointer                    remove_user_data,
+                        GDestroyNotify              remove_notify)
+{
+  ShellMenuTracker *tracker = g_slice_new0 (ShellMenuTracker);
+
+  tracker->ref_count = 1;
+  tracker->insert_func = insert_func;
+  tracker->insert_user_data = insert_user_data;
+  tracker->insert_notify = insert_notify;
+  tracker->remove_func = remove_func;
+  tracker->remove_user_data = remove_user_data;
+  tracker->remove_notify = remove_notify;
+
+  tracker->tracker = gtk_menu_tracker_new (model,
+                                           TRUE, /* with separators */
+                                           action_namespace,
+                                           shell_menu_tracker_insert_func,
+                                           shell_menu_tracker_remove_func,
+                                           tracker);
+
+  return tracker;
+}
+
+ShellMenuTracker *
+shell_menu_tracker_ref (ShellMenuTracker *tracker)
+{
+  tracker->ref_count++;
+  return tracker;
+}
+
+void
+shell_menu_tracker_unref (ShellMenuTracker *tracker)
+{
+  if (tracker->ref_count-- <= 0)
+    {
+      shell_menu_tracker_destroy (tracker);
+      g_slice_free (ShellMenuTracker, tracker);
+    }
+}
+
+void
+shell_menu_tracker_destroy (ShellMenuTracker *tracker)
+{
+  if (tracker->tracker != NULL)
+    {
+      gtk_menu_tracker_free (tracker->tracker);
+      tracker->tracker = NULL;
+      tracker->insert_notify (tracker->insert_user_data);
+      tracker->remove_notify (tracker->remove_user_data);
+    }
+}
+
+G_DEFINE_BOXED_TYPE(ShellMenuTracker,
+                    shell_menu_tracker,
+                    shell_menu_tracker_ref,
+                    shell_menu_tracker_unref)
diff --git a/src/shell-menu-tracker.h b/src/shell-menu-tracker.h
new file mode 100644 (file)
index 0000000..9af602e
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2013 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Written by:
+ *     Jasper St. Pierre <jstpierre@mecheye.net>
+ */
+
+#ifndef __SHELL_MENU_TRACKER_H__
+#define __SHELL_MENU_TRACKER_H__
+
+#include <gio/gio.h>
+
+typedef struct _ShellMenuTracker ShellMenuTracker;
+
+GType shell_menu_tracker_get_type (void) G_GNUC_CONST;
+
+typedef void         (* ShellMenuTrackerInsertFunc)       (gint                      position,
+                                                           GMenuModel               *model,
+                                                           gint                      item_index,
+                                                           const gchar              *action_namespace,
+                                                           gboolean                  is_separator,
+                                                           gpointer                  user_data);
+
+typedef void         (* ShellMenuTrackerRemoveFunc)       (gint                      position,
+                                                           gpointer                  user_data);
+
+ShellMenuTracker * shell_menu_tracker_new (GMenuModel                 *model,
+                                           const gchar                *action_namespace,
+                                           ShellMenuTrackerInsertFunc  insert_func,
+                                           gpointer                    insert_user_data,
+                                           GDestroyNotify              insert_notify,
+                                           ShellMenuTrackerRemoveFunc  remove_func,
+                                           gpointer                    remove_user_data,
+                                           GDestroyNotify              remove_notify);
+ShellMenuTracker * shell_menu_tracker_ref (ShellMenuTracker *tracker);
+void shell_menu_tracker_unref (ShellMenuTracker *tracker);
+void shell_menu_tracker_destroy (ShellMenuTracker *tracker);
+
+#endif /* __SHELL_MENU_TRACKER_H__ */